
/**
 * Copyright (c) 2025 ~ 2026 KylinSec Co., Ltd.
 * kiran-authentication-devices is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 *
 * Author:     yangfeng <yangfeng@kylinsec.com.cn>
 */

#include <dlfcn.h>
#include <qt5-log-i.h>
#include <sys/time.h>
#include <unistd.h>
#include <QByteArray>
#include <QCryptographicHash>

#include "aratekfp.h"
#include "auth_device_adaptor.h"
#include "driver/fingerprint/fp-aratek-driver.h"
#include "feature-db.h"
#include "fp-aratek-device.h"
#include "utils.h"

namespace Kiran
{

REGISTER_DEVICE(FINGERPRINT_ARATEK_DRIVER_NAME, FPAratekDevice);

FPAratekDevice::FPAratekDevice(const QString& vid, const QString& pid, DriverPtr driver, QObject* parent)
    : BioDevice{vid, pid, driver, parent}
{
    setDeviceType(DEVICE_TYPE_FingerPrint);
    setDriverName(FINGERPRINT_ARATEK_DRIVER_NAME);
    setMergeTemplateCount(FP_ARATEK_MEGER_TEMPLATE_COUNT);
    m_driver = driver.dynamicCast<FPAratekDriver>();
}

FPAratekDevice::~FPAratekDevice()
{
    KLOG_DEBUG() << "destroy FPAratek Device";
    acquireFeatureStop();

    if (m_driver->isDeviceOpened())
    {
        m_driver->closeDevice();
    }
}
bool FPAratekDevice::initDevice()
{
    m_driver->openDevice();
    return true;
}

QByteArray FPAratekDevice::acquireFeature()
{
    KLOG_DEBUG() << "start acquire feature";
    int ret = 0;
    if (ret != GENERAL_RESULT_OK)
    {
        KLOG_ERROR() << "Open Device Fail! id:" << deviceID();
        return QByteArray();
    }

    unsigned char featureBuf[FEATURE_LENGTH];
    ret = m_driver->acquireFeature(featureBuf);

    if (ret != 0)
    {
        return QByteArray();
    }

    QByteArray bufferFeature((char*)featureBuf, FEATURE_LENGTH);
    return bufferFeature;
}

void FPAratekDevice::acquireFeatureStop()
{
    m_doAcquire = true;
}
void FPAratekDevice::acquireFeatureFail()
{
    KLOG_DEBUG() << "acquire Feature Fail";
    if (deviceStatus() == DEVICE_STATUS_DOING_ENROLL)
    {
        notifyEnrollProcess(ENROLL_PROCESS_ACQUIRE_FEATURE_FAIL);
        internalStopEnroll();
    }
    else if (deviceStatus() == DEVICE_STATUS_DOING_IDENTIFY)
    {
        notifyIdentifyProcess(IDENTIFY_PROCESS_ACQUIRE_FEATURE_FAIL);
        internalStopIdentify();
    }
}
void FPAratekDevice::templateMerge()
{
    // 由于SDK没有合并接口，这里简单相加，比对时，需要将特征模板分离成三个
    KLOG_DEBUG() << "start enroll template merge";
    QByteArray mergedTemplate = m_enrollTemplates.value(0) + m_enrollTemplates.value(1) + m_enrollTemplates.value(2);
    if (mergedTemplate.isEmpty())
    {
        // 合成模板失败，判定为录入失败，需要重新录入
        notifyEnrollProcess(ENROLL_PROCESS_MEGER_FAIL);
        return;
    }

    //
    QString featureID = QCryptographicHash::hash(mergedTemplate, QCryptographicHash::Md5).toHex();
    if (saveTemplate(mergedTemplate, featureID))
    {
        notifyEnrollProcess(ENROLL_PROCESS_SUCCESS, featureID);
    }
    else
    {
        notifyEnrollProcess(ENROLL_PROCESS_SAVE_FAIL);
    }
}

void FPAratekDevice::notifyEnrollProcess(EnrollProcess process, const QString& featureID)
{
    QString message;
    switch (process)
    {
    case ENROLL_PROCESS_ACQUIRE_FEATURE_FAIL:
        message = tr("acquire fingerprint fail!");
        Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_STATUS_FAIL, message);
        break;
    case ENROLL_PROCESS_PASS:
        message = tr("Partial fingerprint feature entry,please continue to press your fingers");
        Q_EMIT m_dbusAdaptor->EnrollStatus("", enrollTemplatesFromCache().count() * 25, ENROLL_STATUS_PASS, message);
        break;
    case ENROLL_PROCESS_REPEATED_ENROLL:
        message = tr("The fingerprint has been enrolled");
        Q_EMIT m_dbusAdaptor->EnrollStatus(featureID, 0, ENROLL_STATUS_FAIL, message);
        break;
    case ENROLL_PROCESS_INCONSISTENT_FEATURE:
        message = tr("Please place the same finger!");
        KLOG_DEBUG() << message;
        Q_EMIT m_dbusAdaptor->EnrollStatus("", enrollTemplatesFromCache().count() * 25, ENROLL_STATUS_RETRY, message);
        break;
    case ENROLL_PROCESS_MEGER_FAIL:
        message = tr("Failed to enroll fingerprint, please enroll again");
        Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_STATUS_FAIL, message);
        break;
    case ENROLL_PROCESS_SUCCESS:
        message = tr("Successed save finger");
        Q_EMIT m_dbusAdaptor->EnrollStatus(featureID, 100, ENROLL_STATUS_COMPLETE, message);
        break;
    case ENROLL_PROCESS_SAVE_FAIL:
        message = tr("Save Finger Failed!");
        Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_STATUS_FAIL, message);
        break;
    case ENROLL_PROCESS_INCONSISTENT_FEATURE_AFTER_MERGED:
        message = tr("Failed to enroll fingerprint, please enroll again");
        Q_EMIT m_dbusAdaptor->EnrollStatus("", 0, ENROLL_STATUS_FAIL, message);
        break;
    default:
        break;
    }
    if (!message.isEmpty())
    {
        if (!featureID.isEmpty())
        {
            KLOG_DEBUG() << QString("%1, feature id:%2").arg(message).arg(featureID);
        }
        else
        {
            KLOG_DEBUG() << message;
        }
    }
}
void FPAratekDevice::notifyIdentifyProcess(IdentifyProcess process, const QString& featureID)
{
    QString message;
    switch (process)
    {
    case IDENTIFY_PROCESS_TIME_OUT:
        break;
    case IDENTIFY_PROCESS_ACQUIRE_FEATURE_FAIL:
        message = tr("acquire fingerprint fail!");
        Q_EMIT m_dbusAdaptor->IdentifyStatus("", IDENTIFY_STATUS_NOT_MATCH, message);
        break;
    case IDENTIFY_PROCESS_MACTCH:
        message = tr("Fingerprint match");
        Q_EMIT m_dbusAdaptor->IdentifyStatus(featureID, IDENTIFY_STATUS_MATCH, message);
        break;
    case IDENTIFY_PROCESS_NO_MATCH:
        message = tr("Fingerprint not match, place again");
        Q_EMIT m_dbusAdaptor->IdentifyStatus(featureID, IDENTIFY_STATUS_NOT_MATCH, message);
        break;
    default:
        break;
    }
    if (!message.isEmpty())
    {
        if (!featureID.isEmpty())
        {
            KLOG_DEBUG() << QString("%1, feature id:%2").arg(message).arg(featureID);
        }
        else
        {
            KLOG_DEBUG() << message;
        }
    }
}

// 对比两枚指纹是否匹配
int FPAratekDevice::templateMatch(QByteArray fpTemplate1, QByteArray fpTemplate2)
{
    int ret = m_driver->templateMatch(fpTemplate1, fpTemplate2);
    return ret;
}

QString FPAratekDevice::identifyFeature(QByteArray feature, QList<QByteArray> existedfeatures)
{
    QString featureID;
    int ret = 0;
    for (int j = 0; j < existedfeatures.count(); j++)
    {
        // 由于SDK没有提供合并接口，我们保存的特征是三个相加的特征（见FPAratekDevice::templateMerge），这里需要分离
        auto saveTemplate = existedfeatures.value(j);
        for (int i = 0; i < saveTemplate.size(); i += FEATURE_LENGTH)
        {
            ret = templateMatch(feature, saveTemplate.mid(i, FEATURE_LENGTH));
            // 指纹已经存在，直接返回该指纹
            if (ret == GENERAL_RESULT_OK)
            {
                featureID = FeatureDB::getInstance()->getFeatureID(saveTemplate);
                break;
            }
        }
        if (ret == GENERAL_RESULT_OK)
        {
            break;
        }
    }

    return featureID;
}

}  // namespace Kiran